<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package cs5620.object;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import javax.vecmath.Vector2f;
import javax.vecmath.Vector3f;

/**
 *
 * @author Karl
 */
public class ParameterizedTerrainMaker implements ParameterizedObjectMaker {

  public interface TerrainCallback {
    public abstract float getTerrainHeight(float x,
                                           float y,
                                           Vector3f returnedNormal);

    // Obtains the area in space that this terrain requires in order to fully
    // express all of its features
    public abstract void getExtent(Vector2f min, Vector2f max);
  }

  public class CollectionOfTerrainCallbacks implements TerrainCallback {
    ArrayList&lt;TerrainCallback&gt; callbacks = new ArrayList&lt;TerrainCallback&gt;();
    void add(TerrainCallback tc) { callbacks.add(tc); }
    float averagingDistance;
    public CollectionOfTerrainCallbacks(float averagingDistance) {
      this.averagingDistance = averagingDistance;
    }

    public float getTerrainHeight(float x,
                                  float y,
                                  Vector3f returnedNormal) {
      Vector3f a = new Vector3f();
      Vector3f b = new Vector3f();
      Vector3f c = new Vector3f();
      Vector3f d = new Vector3f();
      float ah = getTerrainHeightAt(x-averagingDistance,y-averagingDistance,a);
      float bh = getTerrainHeightAt(x+averagingDistance,y-averagingDistance,b);
      float ch = getTerrainHeightAt(x-averagingDistance,y+averagingDistance,c);
      float dh = getTerrainHeightAt(x+averagingDistance,y+averagingDistance,d);
//      a.cross(new Vector3f(1.0f,dh-ah,1.0f),
//              new Vector3f(1.0f,dh-ch,-1.0f));
      returnedNormal.set(a);
      returnedNormal.add(b);
      returnedNormal.add(c);
      returnedNormal.add(d);
      returnedNormal.normalize();
      return (ah+bh+ch+dh)/4.0f;
    }

    public float getTerrainHeightAt(float x,
                                    float y,
                                    Vector3f returnedNormal) {
      float h = 0.0f;
      Vector3f normalSum = new Vector3f();
      for (TerrainCallback tc: callbacks) {
        h += tc.getTerrainHeight(x, y, returnedNormal);
        normalSum.add(returnedNormal);
      }
      if (callbacks.size() &gt; 1) {
        h /= (float)callbacks.size();
      }
      //returnedNormal.normalize();
      return h;
    }

    public void getExtent(Vector2f min, Vector2f max) {
      Vector2f minOut = null, maxOut = null;
      for (TerrainCallback tc: callbacks) {
        Vector2f tcMin = new Vector2f();
        Vector2f tcMax = new Vector2f();
        tc.getExtent(tcMin, tcMax);
        if (minOut == null) minOut = tcMin;
        if (maxOut == null) maxOut = tcMax;
        if (tcMin.x &lt; minOut.x) minOut.x = tcMin.x;
        if (tcMax.x &gt; maxOut.x) maxOut.x = tcMax.x;
        if (tcMin.y &lt; minOut.y) minOut.y = tcMin.y;
        if (tcMax.y &gt; maxOut.y) maxOut.y = tcMax.y;
      }
      min.set(minOut);
      max.set(maxOut);
    }
  }

  public class HillTerrainCallback implements TerrainCallback {
    private float x, y, radius, height, buffer;

    // (x,y) is center
    // radius = size
    // buffer = amt. added to radius for getExtent, but not affected by
    //   hill at all.
    public HillTerrainCallback(float x,
                               float y,
                               float radius,
                               float height,
                               float buffer) {
      this.x = x;
      this.y = y;
      this.radius = radius;
      this.height = height;
      this.buffer = buffer;
    }

    public float getTerrainHeight(float x,
                                  float y,
                                  Vector3f returnedNormal) {
      float dx = x-this.x;
      float dy = y-this.y;
      float dsq = dx*dx+dy*dy;
      float rsq = radius*radius;
      if (dsq &lt; rsq) {
        float returnedHeight = (1-dsq/rsq) * height;
        returnedNormal.set(dx, returnedHeight, dy);
        returnedNormal.normalize();
        return returnedHeight;
      } else {
        return 0.0f;
      }
    }

    public void getExtent(Vector2f min, Vector2f max) {
      min.set(x-radius-buffer, y-radius-buffer);
      max.set(x+radius+buffer, y+radius+buffer);
    }
  }

//  public static void main(String args[][]) {
//
//  }

  /**
   * Generates a hierarchial object that represents a ground mesh.  The
   * parameters include:
   *  1- number of rows
   *  2- number of columns
   *  3- an object that generates the height and normal of a terrain vertex
   *     given some (x,y) coordinate.  This object must implement the
   *     TerrainCallback interface
   *
   * The terrain is contained within the area (-1,-1,-1) to (+1,+1,+1).  All
   * returned heights are scaled unformly so that this becomes true.  The
   * returned world area is also initialized such that it contains the complete
   * area required by the TerrainCallback, but after writing the information
   * the terrain is re-scaled back to the unit bounding box.
   *
   * The texture coordinates of the terrain uniformly stretch map a single
   * texture across its entire surface.  There is no attempt to reduce the
   * mag/min that occurs when nearby heights are significantly different.
   *
   * @param inputs
   * @return
   */
  @Override
  public HierarchicalObject make(Object... inputs) {
    if (inputs.length &lt; 4) {
      if (inputs.length == 0 || inputs.length == 1 &amp;&amp; inputs[0] == null) {
        // create default inputs
        inputs = new Object[4];
        inputs[0] = new Integer(100);
        inputs[1] = new Integer(100);
        CollectionOfTerrainCallbacks collection
                = new CollectionOfTerrainCallbacks(0.05f);
        for (int i = (int)(Math.random()*10); i &gt;= 0; --i) {
          collection.add(new HillTerrainCallback((float)Math.random()*2-1,
                                                 (float)Math.random()*2-1,
                                                 (float)(Math.random()+0.5),
                                                 (float)(Math.random()+0.5),
                                                 (float)(Math.random()+0.5)));
        }
        inputs[2] = collection;
        inputs[3] = "Terrain" + HierarchicalObject.getUniqueID();
      } else {
        System.out.println("Invalid use of make() for terrain");
        return null;
      }
    }
    
    int rows = ((Integer)inputs[0]);
    int cols = ((Integer)inputs[1]);
    TerrainCallback terrainCallback = (TerrainCallback)inputs[2];
    String name = (String)inputs[3];

    class Quad {
      private Vector3f vertices[] = new Vector3f[4];
      private Vector3f normals[] = new Vector3f[4];
      public Quad(float left, float top, float right, float bottom) {
        vertices[0] = new Vector3f(left, 0.0f, top);
        vertices[1] = new Vector3f(right, 0.0f, top);
        vertices[2] = new Vector3f(left, 0.0f, bottom);
        vertices[3] = new Vector3f(right, 0.0f, bottom);
      }

      // obtains the vertex heights &amp; normals for each of the coordinates
      public void doCallback(TerrainCallback callback) {
        for (int i = 0; i &lt; 4; ++i) {
          normals[i] = new Vector3f(0.0f, 1.0f, 0.0f);
          vertices[i].y = callback.getTerrainHeight(vertices[i].x,
                                                    vertices[i].z,
                                                    normals[i]);
        }
      }

      // used to initialize min/max search
      public Vector3f coordinate() { return new Vector3f(vertices[0]); }

      // updates min &amp; max to reflect what's in this quad
      public void minMax(Vector3f min, Vector3f max) {
        for (int i = 0; i &lt; 4; ++i) {
          float test;
          test = vertices[i].x;
          if (test &lt; min.x) min.x = test;
          if (test &gt; max.x) max.x = test;
          test = vertices[i].y;
          if (test &lt; min.y) min.y = test;
          if (test &gt; max.y) max.y = test;
          test = vertices[i].z;
          if (test &lt; min.z) min.z = test;
          if (test &gt; max.z) max.z = test;
        }
      }

      // writes out vertices &amp; triangles scaled on each axis such that the
      // bounding box of the output scene specifies [-1,-1,-1] to [+1,+1,+1]
      // given the bounding box of the scene.
      public void write(Vector3f min,
                        Vector3f max,
                        FloatBuffer vertices,
                        FloatBuffer normals,
                        IntBuffer triangles,
                        FloatBuffer texCoords) {
        float dx = max.x - min.x,
              dy = max.y - min.y,
              dz = max.z - min.z;
        // write out vertices
        int firstVertexIndex = vertices.position()/3;
        for (int i = 0; i &lt; 4; ++i) {
          // coordinates in [0,0,0] to [1,1,1]
          //  ... ? ...  :  ... protects from / 0
          float unitX = dx &gt; 0.0f ? (this.vertices[i].x - min.x) / dx : min.x;
          float unitY = dy &gt; 0.0f ? (this.vertices[i].y - min.y) / dy : min.y;
          float unitZ = dz &gt; 0.0f ? (this.vertices[i].z - min.z) / dz : min.z;
          vertices.put(unitX * 2 - 1);
          vertices.put(unitY * 2 - 1);
          vertices.put(unitZ * 2 - 1);
          normals.put(this.normals[i].x);
          normals.put(this.normals[i].y);
          normals.put(this.normals[i].z);
          texCoords.put(unitX);
          texCoords.put(unitY);
        }

        // write out two triangles
        triangles.put(firstVertexIndex + 0);
        triangles.put(firstVertexIndex + 1);
        triangles.put(firstVertexIndex + 2); // end triangle 1
        triangles.put(firstVertexIndex + 2);
        triangles.put(firstVertexIndex + 1);
        triangles.put(firstVertexIndex + 3); // end triangle 2
      }
    }

    Vector2f extentMin = new Vector2f(),
             extentMax = new Vector2f();
    terrainCallback.getExtent(extentMin, extentMax);

    Vector3f min = null, max = null;
    ArrayList&lt;Quad&gt; quads = new ArrayList&lt;Quad&gt;();
    for (int row = 0; row &lt; rows; ++row) {
      float top    = ((float)row  ) / (float)(rows)
                     * (extentMax.y - extentMin.y) + extentMin.y;
      float bottom = ((float)row+1) / (float)(rows)
                     * (extentMax.y - extentMin.y) + extentMin.y;
      for (int col = 0; col &lt; cols; ++col) {
        float left  = ((float)col  ) / (float)(cols)
                      * (extentMax.x - extentMin.x) + extentMin.x;
        float right = ((float)col+1) / (float)(cols)
                      * (extentMax.x - extentMin.x) + extentMin.x;
        Quad q = new Quad(left, top, right, bottom);
        q.doCallback(terrainCallback);
        if (min == null) min = q.coordinate();
        if (max == null) max = q.coordinate();
        q.minMax(min,max);
        quads.add(q);
      }
    }

    // Generate geometry from the set of quads
    float vertices[] = new float[quads.size()*4*3];
    float normals[] = new float[quads.size()*4*3];
    int triangles[] = new int[quads.size()*2*3];
    float texCoords[] = new float[quads.size()*4*2];

    FloatBuffer fbVertices = FloatBuffer.wrap(vertices);
    FloatBuffer fbNormals  = FloatBuffer.wrap(normals);
    IntBuffer ibTriangles  = IntBuffer.wrap(triangles);
    FloatBuffer fbTexCoords= FloatBuffer.wrap(texCoords);

    for (Quad q : quads) {
      q.write(min, max, fbVertices, fbNormals, ibTriangles, fbTexCoords);
    }

    printBoundingBox(vertices, triangles);

    return new TangentSpaceMeshObject(vertices, triangles, normals, texCoords, name);
    //return PrimitiveFactory.makePlane("wtf");
  }

  public static void printBoundingBox(float vertices[], int triangles[]) {
      int tris = triangles.length / 3;

      // uncomment this to validate that the bounding box of the
      // cone matches that specified in the description
      Vector3f min = new Vector3f(vertices[0], vertices[1], vertices[2]),
               max = new Vector3f(vertices[3], vertices[4], vertices[5]);
      for (int t = 0; t &lt; tris; ++t) {
        float test;
        for (int vtx = 0; vtx &lt; 3; ++vtx) {
          try {
          test = vertices[triangles[t*3+vtx] + 0];
          if (test &lt; min.x) min.x = test;
          if (test &gt; max.x) max.x = test;
          test = vertices[triangles[t*3+vtx] + 1];
          if (test &lt; min.y) min.y = test;
          if (test &gt; max.y) max.y = test;
          test = vertices[triangles[t*3+vtx] + 2];
          if (test &lt; min.z) min.z = test;
          if (test &gt; max.z) max.z = test;
          } catch (Exception e) {
            System.out.println(e);
          }
        }
      }
      System.out.println("bounding area: " + min + " to " + max);
  }
}
</pre></body></html>